package plogg;

import static util.Debug.log;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;

import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import util.RFCOMMInputStream;

/**
 * Handles bluetooth connections to ploggs and formats incoming data to the appropriate 
 * internal representation. Use all static methods and pass bluetooth ID.
 * Package private. All external interactions are done through plogg.Plogg
 */

public class BluetoothDevice	{
	public static final int READING_INTERVAL = 1;

	public static final int MAX_CONNECTION_RETRY_ATTEMPTS = 10;
	
	/* ------------------------------------------------------------------------------------
	 *  Implements the singleton pattern, get an instance by calling .getInstance
	 *  Constructor protected.
	 * ------------------------------------------------------------------------------------
	 */
	
	private static BluetoothDevice _instance = null;
	
	private BluetoothDevice()	{
	}
	
	public  static BluetoothDevice getInstance()	{
		if (null == _instance){
			_instance = new BluetoothDevice();
		}
		
		return _instance;
	}
	
	
	/**
	 * Method which will return all appropriate Bluetooth IDs
	 * @return A HashSet containing the list of devices as a list of Strings
	 */
	
	public HashSet<String> getAllDevices()	{
		HashSet<String> fakelist = new HashSet<String>();
		fakelist.add("e1:00:00:a5:5b");
		fakelist.add("e1:00:00:a5:5b");
		fakelist.add("e1:00:22:a5:5b");
		return fakelist;
	}
	
	/**
	 * Method to get all the latest entries from a plogg
	 * @param ploggID	A string containing the ID of the plogg which is being accessed
	 * @param from		The date from which the log should be read from
	 * @return 			ArrayList containing the log from the plogg
	 */
	
	public ArrayList<LogEntry> getLatestEntries(String ploggID)	{
		ArrayList<LogEntry> entries = new ArrayList<LogEntry>();
		
		// ... connect to device, read back entries, verify 
		// that they are from the right date and time etc.
		connect(ploggID);
		
		try	{
			// So far just reads all the replies and lets them be logged in the console
			sendCommand("sd");
			while( getResponse() != null );
		}
		catch(Exception e)	{
			e.printStackTrace();
		}
		finally	{
			disconnect();
		}
		
		return entries;
	}
	
	/**
	 *  method to set all the default values of a specific plogg, the date, timing interval etc.
	 * @param ploggID	String containing the ID of the plogg being accessed
	 * @param ploggName	Name of the plogg being accessed
	 */
	
	public void setPloggDefaults(String ploggID, String ploggName)	{
		// Clear the plogg entries, set the date and what stats to record, the interval etc.
		// all to our default values. perhaps have some constants to signify these. Apart
		// from date. Obviously.
		
		connect(ploggID);
		
		try	{
			// Set Plogg name
			
			// Set the reading interval only if it is not already set properly
			if(false)	{	// TODO: check settings to see if this is necessary
				sendCommand("si " + READING_INTERVAL);
				getResponse("Logging interval is " + READING_INTERVAL + " minute(s)");
			}
			
			// Check the logged values, fix if necessary - Don't change unless necessary
			// it will wipe the already logged values!
			
			// Set Plogg time
			
		}
		catch(Exception e)	{
			e.printStackTrace();
		}
		finally	{
			disconnect();
		}
	}
	
	// ---------------------------------------------------------------------------------
	// Bluetooth connection stuff. Fields store the connection persistently
	// ---------------------------------------------------------------------------------
	
	private String 				connectedTo = null;
	private StreamConnection 	con 		= null;
	private DataOutputStream	out			= null;
	private RFCOMMInputStream	in			= null;
	
	/**
	 *  method to initiate a connection to a plogg
	 * @param ploggID	String containing the ID of the plogg being accessed.
	 */
	
	private int	connectionAttempts = 0;
	
	private void connect(String ploggID)	{
		connectedTo = ploggID;
		
		try	{
			con = (StreamConnection) Connector.open("btspp://" + connectedTo + ":1");
			
			out = con.openDataOutputStream();
			in	= new RFCOMMInputStream(con);
			
			// Some strings which we are not concerned by and so should ignore
			// as they are primarily for direct human users
			in.ignoreString("INVALID COMMAND.  TYPE \'??\' FOR HELP");
			in.ignoreString("  TYPE \'??\' FOR HELP");
		}
		catch (IOException e)	{
			if(connectionAttempts < MAX_CONNECTION_RETRY_ATTEMPTS)	{
				// Have a nap and let whatever catch up...
				log("! Failed to connect, retrying");
				try {
					Thread.sleep(50);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				connectionAttempts++;
				connect(ploggID);
			}
			else	{
				log("! Connection failed");
				disconnect();
			}
		}
		
	}
	
	
	
	/**
	 *  method to terminate a connection to a plogg.
	 */
	private void disconnect()	{
		try	{
			if (con != null && out != null && in != null)	{
				con.close();
				out.close();
				in .close();
			}
			
			con = null;
			out = null;
			in  = null;
			
			connectedTo = null;
		}
		catch(Exception e)	{
			System.err.println("Could not close connection");
			e.printStackTrace();
			System.exit(-1);
		}
	}
	
	/**
	 *  method to send a command to a plogg, after a connection has been established
	 * @param command	String containing the command to be sent to the connected plogg
	 */
	
	private void sendCommand(String command)	{
		if ( connectedTo == null )	{
			log("Not connected to a plogg. Call connect first.");
		}
		else	{
			try	{
				log("> " + command);
				out.writeChars(command + "\n");
				out.flush();
			}
			catch(IOException e)	{
				log("IO failure sending the command. Is the plogg still on and in range?");
								
				e.printStackTrace();
			}
		}
	}
	
	/**
	 *  method to read back from the plogg after a command is sent
	 * @return String containing the response from the plogg
	 */
	private String getResponse()	{
		if ( connectedTo == null )	{
			log("Not connected to a plogg. Call connect first.");
			return null;
		}
		else	{
			try	{
				String nextLine = in.readLine();
				log("< " + nextLine);
				return nextLine;
			}
			catch(IOException e)	{
				log("IO failure. Is the plogg still on and in range?");
							
				return null;
			}
		}
	}
	
	/**
	 *  method which is called if an empty String is read from the plogg
	 * @param expected	String containing what should have been recieved.
	 */
	private void getResponse(String expected)	throws Exception	{
		String response = getResponse();
		if ( ! expected.equals(response)	)	{
			throw new Exception("Expecting: \"" + expected 
						  + "\"\nGot:       \"" + response + "\"");
		}
	}
	
	// ---------------------------------------------------------------------------------
	// Below this line is play stuff, above is final interface
	// ---------------------------------------------------------------------------------
	
	// Store array of available devices persistently
	 ArrayList<RemoteDevice> discoveredDevices = new ArrayList<RemoteDevice>();
	
	// Scans for all bluetooth devices in range:
	public void bluetoothScan() throws IOException, InterruptedException 	{
		final Object inquiryCompletedEvent = new Object();

		DiscoveryListener listener = new DiscoveryListener() {

            public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
                System.out.println("Device " + btDevice.getBluetoothAddress() + " found");
                discoveredDevices.add(btDevice);
                try {
                    System.out.println("     name " + btDevice.getFriendlyName(false));
                } catch (IOException cantGetDeviceName) {
                }
            }

            public void inquiryCompleted(int discType) {
                System.out.println("Device Inquiry completed!");
                synchronized(inquiryCompletedEvent){
                    inquiryCompletedEvent.notifyAll();
                }
            }

            public void serviceSearchCompleted(int transID, int respCode) {
            }

            public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
            }        
            
            public void checkForPlogg(RemoteDevice device) {
            	
            }
        };


        synchronized(inquiryCompletedEvent) {
            boolean started = LocalDevice.getLocalDevice().getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC, listener);
            if (started) {
                System.out.println("wait for device inquiry to complete...");
                inquiryCompletedEvent.wait();
                System.out.println(discoveredDevices.size() +  " device(s) found");
            }
        }
	}
}